package ar.edu.caece.pl.alex.manager.impl;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import ar.edu.caece.pl.alex.manager.IAnalizadorLexico;
import ar.edu.caece.pl.alex.model.IToken;
import ar.edu.caece.pl.alex.model.impl.Token;

public class AnalizadorLexico implements IAnalizadorLexico {
	
	/* CARACTERES PRINCIPALES */
	private String caracterActual;
	private String caracterSiguiente;

	/* ELEMENTOS AUXILIARES */
	private BufferedReader br;
	private boolean primeraLectura = true;
	private ParserHelper parser = new ParserHelper();
	private int tempFila,tempColumna; //estas coordenadas se actualizan con cada lectura
	private int fila,columna;  		  //estas guardan el inicio de cada token
	
	/* INICIALIZACION */
	public AnalizadorLexico(InputStream is) {
		if (is != null) {			
			DataInputStream in = new DataInputStream(is);
			this.br = new BufferedReader(new InputStreamReader(in));
			this.leer();	//primera lectura
		} else {
			throw new IllegalArgumentException("No se asigno fuente para leer");
		}
	}

	/* METODO PRINCIPAL */
	@Override
	public IToken obtSiguienteToken() {
		
		IToken token = null;
		
		if (!this.caracterActual.equals("EOF")) {
			
			/*Primer nivel del Automata Finito: arrancando por Q0:
			  ====================================================
		(Q0 no tiene ciclos, es una simple seleccion, luego si podran haber ciclos)*/
			
			switch (this.caracterActual.charAt(0)){
				case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':
				case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p':
				case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x':
				case 'y': case 'z':
					token = this.parser.reconocerIdsPalabrasReservadas(this);
					this.estamparCoordenadas(token);
					break;
				
				case '\'': case '{':
					token = this.parser.reconocerComentariosyCadenas(this);
					this.estamparCoordenadas(token);
					break;
				
				case ':': case '+': case '-': case '*': case '/': //OJO: en el TXT hay un caracter "�" que no es igual al "-".�-�-� 
					token = this.parser.reconocerOperadores(this);
					this.estamparCoordenadas(token);
					break;
				
				case '(': case ')': case '[': case ']': 
					token = this.parser.reconocerParentesisCorchetes(this);
					this.estamparCoordenadas(token);
					break;
				
				case '<': case '=': case '>': 
					token = this.parser.reconocerComparadores(this);
					this.estamparCoordenadas(token);
					break;

				case ',': case ';': case ' ': case '\n': case '\t': case '\r': case '\f': case '\b': 
					token = this.parser.reconocerSeparadores(this);
					this.estamparCoordenadas(token);
					break;
				
				case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
				case '8': case '9':
					token = this.parser.reconocerNumeros(this);
					this.estamparCoordenadas(token);
				break;
				
				default:
					token = this.parser.reconocerErrores(this);
					this.estamparCoordenadas(token);
				break; //opcional
			}
			
		} //No ponemos else porque las funciones de reconocer pueden detectar EOF y salen sin terminar
		
		if(this.caracterActual.equals("EOF")) {			
			//Se lee fin de archivo, retornar token de fin de archivo
			token = new Token(IToken.TYPE_FIN_DE_ARCHIVO);
			this.estamparCoordenadas(token);
			
			// Cerrar el stream
			try {this.br.close();} catch (IOException e) {e.printStackTrace();}
			
			return token;
		}
		
		//Refresco lectura
		this.leer();
		
		//Por ultimo, revisar si este token es retornable al Analizador Sintactico
		//Si no lo es (Comentarios y espacios) volver a empezar
		if (token.isRetornable()) {			
			return token;
		} else {
			return this.obtSiguienteToken(); //llamada recursiva
		}
	}
	
	
	/* OTRAS FUNCIONES AUXILIARES */
	
	/**
	 * La primera vez lee del stream los pr�ximos 2 caracteres y actualiza los caracteres actual y siguiente
	 * El resto de las llamadas pone el siguiente en actual y lee uno para actualizar el siguiente  
	 * Si encuenta fin de archivo, inserta EOF en el caracter que haya leido
	 */
	void leer() {
			
		try {
			if (this.primeraLectura) {
				
				int i = br.read();
				this.primeraLectura = false;
				this.actualizarTempCoords(i);
				
				if (i != -1) {  //Si no es fin de archivo
					
					char c = (char) i;
					this.caracterActual = String.valueOf(c).toLowerCase();
					
					i = br.read();
					this.actualizarTempCoords(i);
					
					if (i != -1) { //ahora revisar si viene un -1 en el 2do, si no viene entonces poner en el siguiente
						
						c = (char) i;
						this.caracterSiguiente = String.valueOf(c).toLowerCase();

					} else { // sino ponerle EOF y luego en el proximo ciclo revisar el caracter
						this.caracterSiguiente = "EOF";
					}
				} else {
					//Se lee fin de archivo, retornar EOF
					this.caracterActual = "EOF";
					return;
				}
				
			} else { //Segunda lectura o posterior

				this.caracterActual = this.caracterSiguiente;

				if (this.caracterActual.equals("EOF")) {
					
					return;  //Si lo que viene es EOF no leer siguiente
					
				} else {
					
					int i = br.read();
					this.actualizarTempCoords(i);
					
					if (i != -1) { //El siguiente no es fin de archivo
						
						char c = (char) i;
						this.caracterSiguiente = String.valueOf(c).toLowerCase();
						
					} else { //El siguiente es fin de archivo
						this.caracterSiguiente = "EOF";
					}
				}
			} //Fin segunda lectura
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void actualizarTempCoords(int i) {
		if (String.valueOf((char)i).equals("\n")){
			this.tempColumna++;		//incremento columna
			this.tempFila=0;		//reinicio fila
		} else {			
			this.tempFila++;	//incremento fila
		}
	}
	
	/** Graba las coordenadas en el token y pasa al siguiente*/
	private void estamparCoordenadas(IToken token){
		token.setCoords(new int[]{this.fila,this.columna});
		this.fila = this.tempFila;
		this.columna = this.tempColumna;
	}
	
	String getActual() {	//Auxiliar: para el ParserHelper
		return this.caracterActual;
	}
	
	String getSiguiente() {	//Auxiliar: para el ParserHelper
		return this.caracterSiguiente;
	}
}
